Skip to content

Fix #13805: phpstan does not recognize keys after array literal merge correctly#4897

Merged
ondrejmirtes merged 2 commits into2.1.xfrom
create-pull-request/patch-ueafbwj
Feb 12, 2026
Merged

Fix #13805: phpstan does not recognize keys after array literal merge correctly#4897
ondrejmirtes merged 2 commits into2.1.xfrom
create-pull-request/patch-ueafbwj

Conversation

@phpstan-bot
Copy link
Collaborator

Summary

When using array literal syntax with spread operators ([...$a, ...$b]), PHPStan lost specific key information from constant array types when the array builder was degraded to a general array. This caused false positives when passing the result to functions expecting specific keys.

For example, [...($defaultItems['test'] ?? []), ...$row] where $row is array{foo: string, muh: string} was typed as non-empty-array<string, mixed> instead of preserving the knowledge that keys foo and muh definitely exist with string values.

Changes

  • Modified src/Reflection/InitializerExprTypeResolver.php in the getArrayType() method:
    • Track HasOffsetValueType accessories for non-optional keys when spreading constant arrays with string keys
    • When a non-constant array is spread afterward that could overwrite tracked keys (its key type is a supertype of the tracked offsets), remove those tracked entries
    • After building the array, if the result is degraded (not a constant array) and there are tracked HasOffsetValueType entries, intersect them with the result type
  • Added regression test in tests/PHPStan/Analyser/nsrt/bug-13805.php

Root cause

In InitializerExprTypeResolver::getArrayType(), when the first spread item is not a single constant array (e.g., array<string, mixed>), the ConstantArrayTypeBuilder is degraded to track only general array types. Subsequent constant array spreads (like ...$row with array{foo: string, muh: string}) have their key/value pairs added to the builder, but getArray() unions all keys and values together, losing the specific offset information.

The fix preserves the specific key knowledge by tracking HasOffsetValueType accessories for non-optional keys from constant array spreads with string keys, and intersecting them with the final degraded array type. This correctly handles ordering: keys from constant arrays spread after non-constant arrays are guaranteed to be present, while keys from constant arrays spread before non-constant arrays are removed if the non-constant array's key type could overwrite them.

Test

Added tests/PHPStan/Analyser/nsrt/bug-13805.php which tests:

  • Spreading a non-constant array ($defaultItems['test'] ?? []) followed by a constant array ($row) preserves the constant array's key information as HasOffsetValueType accessories
  • Spreading a non-constant array followed by a constant array with different keys preserves the specific key knowledge

Fixes phpstan/phpstan#13805

ondrejmirtes and others added 2 commits February 12, 2026 14:31
…ray literals

- Track HasOffsetValueType accessories when spreading constant arrays with string keys in InitializerExprTypeResolver::getArrayType()
- When the array builder degrades to a general array (e.g. due to a non-constant spread), intersect the result with HasOffsetValueType for keys from constant array spreads that appear later
- Properly invalidate tracked offsets when a subsequent non-constant spread could overwrite them
- New regression test in tests/PHPStan/Analyser/nsrt/bug-13805.php
Closes phpstan/phpstan#13805

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ondrejmirtes ondrejmirtes merged commit f375a45 into 2.1.x Feb 12, 2026
624 of 637 checks passed
@ondrejmirtes ondrejmirtes deleted the create-pull-request/patch-ueafbwj branch February 12, 2026 14:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

phpstan does not recognize keys after array literal merge correctly

2 participants